package com.alm.common.utils;

import com.alibaba.fastjson2.JSONObject;
import com.alm.common.auth.ActiveCodeDTO;
import com.alm.common.auth.AuthEnum;
import com.alm.common.auth.CipherInfo;
import com.alm.common.utils.file.FileUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import sun.misc.BASE64Decoder;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;

/**
 * @author ywy
 * @date 2024/8/7
 * 过期授权校验工具
 */
@Slf4j
public class AuthUtil {

    /**
     * 加密文件路径
     */
    public static final String SECRET_FILE_PATH = System.getProperty("user.dir") + "/alm-auth/";
    public static final String SECRET_FILE_PATH_NAME = "secret.almiot";

    /**
     * 随机码文件路径
     */
    public static final String AUTH_FILE_PATH = System.getProperty("user.dir") + "/alm-auth";

    public static final String AUTH_FILE_PATH_NAME = "/ac.almiot";

    private static final String ALL_CHAR_NUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    /**
     * 获取随机长度的串
     *
     * @param length
     * @return
     */
    public static String getStringNumRandom(int length) {
        //生成随机数字和字母,
        Random random = new Random();
        StringBuilder saltString = new StringBuilder(length);
        for (int i = 1; i <= length; ++i) {
            saltString.append(ALL_CHAR_NUM.charAt(random.nextInt(ALL_CHAR_NUM.length())));
        }
        return saltString.toString();
    }

    /**
     * String转私钥PublicKey
     * @param key
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = (new BASE64Decoder()).decodeBuffer(key);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec (keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(keySpec);
        return publicKey;
    }

    /**
     * 公钥（短串）加密，返回密文
     * @param keyStr
     * @param plaintext
     * @return
     * @throws Exception
     */
    public static String encode(String keyStr, String plaintext) {
        if(StringUtils.isBlank(plaintext)) {
            return "";
        }
        try{
            PublicKey publicKey = getPublicKey(keyStr);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            //用公钥初始化此Cipher对象（加密模式）
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            //对数据加密
            byte[] encrypt = cipher.doFinal(plaintext.getBytes());
            //返回base64编码后的字符串
            return Base64.getEncoder().encodeToString(encrypt);
        }catch (Exception e) {
            log.error("明文：{}，加密失败：{}", plaintext, e.getMessage());
        }
        return "";
    }


    /**
     * 公钥（短串）解密，返回明文
     * @param keyStr 公钥
     * @param ciphertext 密文
     * @return
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public static String decode(String keyStr, String ciphertext) {

        if(StringUtils.isBlank(ciphertext)) {
            return "";
        }
        try{
            PublicKey publicKey = getPublicKey(keyStr);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            byte[] bytes = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
            return new String(bytes, StandardCharsets.UTF_8);
        }catch (Exception e) {
            log.error("密文：{}，解密失败：{}", ciphertext, e.getMessage());
        }
        return "";
    }

    /**
     * 加密或解密，返回处理好的字符串
     * @param content
     * @param type
     * @return
     */
    public static String processCrypt(String content, String type) {
        // 获取密钥信息
        String key = FileUtils.readFile("auth/almkey.almiot");
        if(StringUtils.isBlank(key)) {
            log.error("获取到的密钥信息为空！！！！！");
            return "";
        }

        if(AuthEnum.DECODE.name().equalsIgnoreCase(type)) {
            // 解密并返回明文
            return decode(key, content);
        }
        else if (AuthEnum.ENCODE.name().equalsIgnoreCase(type)) {
            // 加密并返回密文
            return encode(key, content);
        }
        return "";
    }

    /**
     * 该方法需要两个文件同时存在才执行，主要用于登录
     * @return
     */
    public static CipherInfo getPlaintextObjForLogin() {
        // 如果两个文件都存在，才需要下一步处理，否则直接返回false
        if(FileUtils.isExists(AUTH_FILE_PATH + AUTH_FILE_PATH_NAME) && FileUtils.isExists(SECRET_FILE_PATH + SECRET_FILE_PATH_NAME)) {

            // 获取加密信息
            String secretInfo = FileUtils.readFileByPath(SECRET_FILE_PATH + SECRET_FILE_PATH_NAME);
            // 解密获取明文
            String plaintext = processCrypt(secretInfo, AuthEnum.DECODE.name());
            CipherInfo cipherInfo = JSONObject.parseObject(plaintext, CipherInfo.class);
            return cipherInfo;
        }
        return  null;
    }

    /**
     * 获取授权对象
     * @return
     */
    public static CipherInfo getPlaintextObj() {
        // 判断加密文件
        if(FileUtils.isExists(SECRET_FILE_PATH + SECRET_FILE_PATH_NAME)) {
            // 获取加密信息
            String secretInfo = FileUtils.readFileByPath(SECRET_FILE_PATH + SECRET_FILE_PATH_NAME);
            // 解密获取明文
            String plaintext = processCrypt(secretInfo, AuthEnum.DECODE.name());
            CipherInfo cipherInfo = JSONObject.parseObject(plaintext, CipherInfo.class);
            return cipherInfo;
        }
        return null;
    }

    /**
     * 校验授权码
     * @param ac 授权码
     * @param cipherInfo 明文信息对象
     * @return
     */
    public static Boolean validateAuth(String ac, CipherInfo cipherInfo) {
        // 绕过
        if(cipherInfo != null && cipherInfo.getExpiredTime().equalsIgnoreCase("alm")) {
            log.warn("绕过授权校验!!!!!");
            return true;
        }

        if(cipherInfo == null || StringUtils.isBlank(cipherInfo.getMac()) || StringUtils.isBlank(cipherInfo.getAuthCode())) {
            return false;
        }

        // 校验授权码和mac
        String macAddress = ComputerUniqueIdUtil.getMACAddress().replace("-", "");
        if(StringUtils.equalsIgnoreCase(macAddress, cipherInfo.getMac()) && StringUtils.equalsIgnoreCase(ac, cipherInfo.getAuthCode())) {
            return true;
        }
        log.warn("授权文件不存在。。。。");
        return false;
    }

    /**
     * 校验激活码并写入
     * @return
     */
    public static Boolean validateActivationCode(ActiveCodeDTO dto) {

        String actCode = dto.getActCode();
        String plaintext = processCrypt(actCode, AuthEnum.DECODE.name());
        CipherInfo cipherInfo = JSONObject.parseObject(plaintext, CipherInfo.class);
        Boolean valid = valid(cipherInfo);
        if(valid) {
            // 写入授权码
            FileUtils.writeFile(SECRET_FILE_PATH_NAME, actCode);
        }
        return valid;
    }

    private static Boolean valid(CipherInfo cipherInfo) {
        if(cipherInfo != null && StringUtils.equalsIgnoreCase(cipherInfo.getExpiredTime(), "alm")) {
            return true;
        }
        if(cipherInfo != null && StringUtils.isNotBlank(cipherInfo.getExpiredTime())
                && StringUtils.isNotBlank(cipherInfo.getAuthCode()) && StringUtils.isNotBlank(cipherInfo.getMac())) {
            return true;
        }
        return false;
    }

    /**
     * 校验剩余时间是否有1个月
     * @param dateStr 格式为 yyyy-MM-dd HH:mm:ss
     * @return
     */
    public static Boolean validateTimeIsBefore(String dateStr){
        if(dateStr.equalsIgnoreCase("alm")){
            return false;
        }
        Date date = TimeUtil.strToDateNoHour(dateStr);
        // 获取一个月前的日期
        Calendar c = Calendar.getInstance();
        c.setTime(date);
        c.add(Calendar.MONTH, -1);
        Date beforeDate = c.getTime();

        long now = System.currentTimeMillis();
        log.info("传入时间为：{}， 传入时间一个月前的时间为：{}, 当前时间为：{}", dateStr, TimeUtil.format(beforeDate), TimeUtil.format(now));
        if(now >= beforeDate.getTime()) {
            return true;
        }
        return false;
    }

    /**
     * 判断使用期限是否已经过期
     * @param dateStr
     * @return
     */
    public static Boolean validateTimeExpiration(String dateStr) {
        if(dateStr.equalsIgnoreCase("alm")){
            return false;
        }
        return System.currentTimeMillis() >= TimeUtil.strToDateNoHour(dateStr).getTime();
    }

    /**
     * 将mac地址、随机码信息加密，并返回加密字符串
     * @return
     */
    public static String generatorInfo() {
        // 获取mac地址
        String macAddress = ComputerUniqueIdUtil.getMACAddress();
        // 获取授权码
        String ac = FileUtils.readFileByPath(AUTH_FILE_PATH + AUTH_FILE_PATH_NAME);
        // 如果授权码为空，重新生成
        if(StringUtils.isBlank(ac)) {
            ac = getStringNumRandom(8);
            FileUtils.writeFile(AUTH_FILE_PATH_NAME, ac);
            log.warn("授权码为空，重新生成授权码！");
        }
        // 生成明文信息
        String ciphertext = FileUtils.readFile("auth/secret.almiot");
        String decode = processCrypt(ciphertext, AuthEnum.DECODE.name());
        CipherInfo cipherInfo = JSONObject.parseObject(decode, CipherInfo.class);
        cipherInfo.setAuthCode(ac);
        cipherInfo.setMac(macAddress);
        // 加密
        String crypt = processCrypt(JSONObject.toJSONString(cipherInfo), AuthEnum.ENCODE.name());
        return crypt;
    }

    /**
     * 初始化授权码
     */
    public static void initAuth(){
        if(FileUtils.isExists(AUTH_FILE_PATH + AUTH_FILE_PATH_NAME)){
            System.out.println("ac文件存在");
            // 获取授权码
            String ac = FileUtils.readFileByPath(AUTH_FILE_PATH + AUTH_FILE_PATH_NAME);
            System.out.println("读取到授权码为：" + ac);
            if(StringUtils.isNotBlank(ac)) {
                // 获取加密文件内容
                if(FileUtils.isExists(SECRET_FILE_PATH + SECRET_FILE_PATH_NAME)) {
                    String secretInfo = FileUtils.readFileByPath(SECRET_FILE_PATH + SECRET_FILE_PATH_NAME);
                    // 获取密钥信息
                    String key = FileUtils.readFile("auth/almkey.almiot");
                    // 解密
                    String plaintext = decode(key, secretInfo);
                    if(StringUtils.isNotBlank(plaintext)) {
                        // 判断mac地址
                        String macAddress = ComputerUniqueIdUtil.getMACAddress().replace("-", "");
                        CipherInfo cipherInfo = JSONObject.parseObject(plaintext, CipherInfo.class);
                        // 如果类为空 或 mac为空 或 mac不匹配
                        if(cipherInfo == null || StringUtils.isBlank(macAddress) || !StringUtils.equalsIgnoreCase(cipherInfo.getMac(), macAddress)) {
                            // 不同重新生成授权码
                            log.warn("设备标识不匹配！！！！！");
                            FileUtils.writeFile(AUTH_FILE_PATH_NAME, getStringNumRandom(8));
                        }

                    }else {
                        // 为空不管了，不需要初始化时处理，直接抛出警告信息
                        log.error("解密到信息为空，请联系管理员");
                    }
                }else {
                    // 不存在直接生成授权码，因为系统自带的加密信息没有客户的mac地址
                    FileUtils.writeFile(AUTH_FILE_PATH_NAME, getStringNumRandom(8));
                }
            }else {
                // 授权码为空，重新生成
                FileUtils.writeFile(AUTH_FILE_PATH_NAME, getStringNumRandom(8));
            }
        }else {
            // 不存在则生成授权码
            FileUtils.writeFile(AUTH_FILE_PATH_NAME, getStringNumRandom(8));
        }
    }

    /**
     * 响应json数据给前端
     *
     * @param response
     * @param obj
     */
    public static void sendJsonMessage(HttpServletResponse response, Object obj) {

        response.setContentType("application/json; charset=utf-8");

        try (PrintWriter writer = response.getWriter()) {
            writer.print(JsonUtil.obj2Json(obj));
            response.flushBuffer();

        } catch (IOException e) {
            log.warn("响应json数据给前端异常:{}",e);
        }

    }

}
